﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Security.Cryptography;
using FileTransfer.BlockCipher;

namespace FileTransfer
{
   

    class FileHoster
    {
        #region Common

        public bool SendFile(string fileName, IPEndPoint ipEndPoint, Algorithm algorithm)
        {
            if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException("fileName");
            if (ipEndPoint == null) throw new ArgumentNullException("ipEndPoint");



            if (!File.Exists(fileName)) return false;
            long fileSize = new FileInfo(fileName).Length;

            try
            {
                ConnectToRemote(ipEndPoint);


                // is the server available?
                SendRequestForSendingFile(fileName, fileSize, ipEndPoint, algorithm);

                // get public RSA key to encrypt the file
                string publicRSAKeyXML = GetRsaKey();

                // generate our own symmetric key that will be encrypted by RSA
                byte[] symKey = GenerateSymmetricKey();

                // encrypt our key to protect from MiM
                byte[] encryptedSymmetricKey = EncryptSymmetricKey(symKey, publicRSAKeyXML);

                // get the file
                byte[] plainFileContent = GetFileContent(fileName);

                // and encrypt it
                byte[] encryptedFileContent = EncryptData(plainFileContent, symKey, algorithm);

                // send encrypted key that we used to encrypt file
                SendSymmetricKey(encryptedSymmetricKey, ipEndPoint);

                // and send file
                SendEncryptedData(encryptedFileContent, ipEndPoint);


                DisconnectFromRemote(ipEndPoint);

            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw;
                //return false;
            }

            return true;
        }

        private byte[] GetFileContent(string fileName)
        {
            return File.ReadAllBytes(fileName);
        }

        #endregion
       


        #region Crypto


        public int SymmetricKeySize
        {
            get
            {
                return symmetricKeySize;
            }
            set
            {
                symmetricKeySize = value;
            }
        }
        private int symmetricKeySize = 256;



        private byte[] EncryptData(byte[] plainData, byte[] symKey, Algorithm algorithm)//, out byte padded)
        {
            // This function is a stub now. In fact it do nothing but XOR.
            // For security purposes recommend to add some symmetric cipher here

            if (symKey.Length <= 0) throw new ArgumentException("The size of symmetric key must be greater than zero");

            
            byte padded;
            BlockCipher.BlockCipher encrypter;

            switch (algorithm)
            {
                case Algorithm.GOST:
                    encrypter = new GOST_89(symKey);
                    break;
                case Algorithm.Blowfish:
                    encrypter = new Blowfish(symKey);
                    break;
                default:
                    return null;
            }
            
            byte[] encryptedData = encrypter.Encipher(plainData, out padded);

            return encryptedData;
        }

        private byte[] EncryptSymmetricKey(byte[] symKey, string publicRSAKeyXML)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(publicRSAKeyXML);
            return rsa.Encrypt(symKey, false);
        }

        private byte[] GenerateSymmetricKey()
        {
            int keySizeInBytes = SymmetricKeySize / 8;

            byte[] symKey = new byte[keySizeInBytes];

            Random r = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < keySizeInBytes; ++i)
            {
                // create random symmetric key
                symKey[i] = (byte)r.Next(0, 255);
            }

            return symKey;
        }

        #endregion



        #region Network


        private TcpClient client;
        private NetworkStream clientStream
        {
            get
            {
                if (client == null) return null;
                return client.GetStream();
            }
        }

        

        private void ConnectToRemote(IPEndPoint ipEndPoint)
        {
            if (client != null) client.Close();

            client = new TcpClient();
            client.Connect(ipEndPoint);
        }

        private void SendRequestForSendingFile(string fileName, long fileSize, IPEndPoint ipEndPoint, Algorithm algorithm)
        {
            List<byte> rawData = new List<byte>(
                fileName.Length +
                SecureFileTransferProtocol.clFileSend.Length +
                fileSize.ToString().Length +
                SecureFileTransferProtocol.clFileSizeSend.Length);


            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.clFileSend));
            rawData.AddRange(Encoding.UTF8.GetBytes(fileName));
            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.delimeter));
            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.clFileSizeSend));
            rawData.AddRange(Encoding.ASCII.GetBytes(fileSize.ToString()));
            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.delimeter));

            int[] alg = new int[1];
            alg[0] = (int)algorithm;
            byte[] algAsBytes = new byte[1 * sizeof(int)];
            Buffer.BlockCopy(alg, 0, algAsBytes, 0, algAsBytes.Length);

            rawData.AddRange(algAsBytes);

            clientStream.Write(rawData.ToArray(), 0, rawData.Count);
        }

        private string GetRsaKey()
        {
            StreamReader readStream = new StreamReader(clientStream);
            string readString = readStream.ReadLine();

            if (!readString.Contains(SecureFileTransferProtocol.srvKeyLength)) return null;


            int keyLength = 0;
            if (!Int32.TryParse(readString.Replace(SecureFileTransferProtocol.srvKeyLength, string.Empty), out keyLength)) return null;

           
            char[] keyContent = new char[keyLength];
            readStream.Read(keyContent, 0, keyLength);
            return new String(keyContent);
        }

        
        

        private void SendEncryptedData(byte[] encryptedContent, IPEndPoint ipEndPoint)
        {
            List<byte> rawData = new List<byte>(
                encryptedContent.Length +
                SecureFileTransferProtocol.clEncryptedFileLength.Length);


            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.clEncryptedFileLength));
            rawData.AddRange(Encoding.ASCII.GetBytes(encryptedContent.Length.ToString()));
            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.delimeter));

            clientStream.Write(rawData.ToArray(), 0, rawData.Count);

            clientStream.Write(encryptedContent, 0, encryptedContent.Length);
        }

        private void SendSymmetricKey(byte[] encryptedSymmetricKey, IPEndPoint ipEndPoint)
        {
            List<byte> rawData = new List<byte>(
                encryptedSymmetricKey.Length +
                SecureFileTransferProtocol.clSymKeyLength.Length);


            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.clSymKeyLength));
            rawData.AddRange(Encoding.ASCII.GetBytes(encryptedSymmetricKey.Length.ToString()));
            rawData.AddRange(Encoding.ASCII.GetBytes(SecureFileTransferProtocol.delimeter));

            clientStream.Write(rawData.ToArray(), 0, rawData.Count);
            clientStream.Write(encryptedSymmetricKey, 0, encryptedSymmetricKey.Length);
        }

        

        private void DisconnectFromRemote(IPEndPoint ipEndPoint)
        {
            if (client != null) client.Close();
        }

        #endregion


    }
}
